using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Unity.Mathematics;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Tilemaps;

public enum TileTypes {START, TARGET, WATER, COLLIDER, PATH, MAIN }

public class Astar : MonoBehaviour
{
    [SerializeField]
    private Camera camera;
    [SerializeField]
    private LayerMask layerMask;
    [SerializeField]
    private Tilemap tilemap;
    [SerializeField]
    private Tile[] tiles;
    public TileTypes tileType;
    private Vector3Int startPos, targetPos;
    private HashSet<Node> openNodes;
    private HashSet<Node> closedNodes;
    private Dictionary<Vector3Int, Node> allNodes=new Dictionary<Vector3Int, Node>();
    private Node currentNode;
    private Stack<Vector3Int> path; 
    private List<Vector3Int> waterTiles = new List<Vector3Int>();
    private HashSet<Vector3Int> changedNodes = new HashSet<Vector3Int>();

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            RaycastHit2D hit = Physics2D.Raycast(camera.ScreenToWorldPoint(Input.mousePosition),Vector2.zero, Mathf.Infinity, layerMask);
            if (hit.collider != null)
            {
                Vector3 mouseWorldPosition = camera.ScreenToWorldPoint(Input.mousePosition);
                Vector3Int mouseV3int =tilemap.WorldToCell(mouseWorldPosition);
                ChangeTile(mouseV3int);
            }
        }
        if (Input.GetKeyDown(KeyCode.V)) 
        {
            AstarAlgorithm();
        }
    }
    public void ChangeTile(Vector3Int mouseV3int)
    {
        if (tileType == TileTypes.START) 
        {
            startPos = mouseV3int;
        }
        else if (tileType == TileTypes.TARGET)
        {
            targetPos = mouseV3int;
        }
        else if (tileType==TileTypes.WATER)
        {
            waterTiles.Add(mouseV3int);
        }
        tilemap.SetTile(mouseV3int, tiles[(int)tileType]);
        changedNodes.Add(mouseV3int);
    }
    public void ChangeTileType(AstarButtons button)
    {
        tileType = button.MyTileType;
    }
    
    public Node GetNode(Vector3Int position)
    {
        if (allNodes.ContainsKey(position))
        {
            return allNodes[position];
        }
        else
        {
            Node node = new Node(position);
            allNodes.Add(position, node);
            return node;
        }
    }

    private void Initialize()
    {
        currentNode = GetNode(startPos);
        openNodes = new HashSet<Node>();
        closedNodes = new HashSet<Node>();
        openNodes.Add(GetNode(startPos));
    }
    public List<Node> GetNeighbours(Vector3Int parentPosition)
    {
        List<Node> neighbours= new List<Node>();
        for (int x =-1; x <= 1; x++)
        {
            for (int y = -1; y <= 1; y++)
            {
                Vector3Int neighbourPos = new Vector3Int(parentPosition.x - x, parentPosition.y - y);
                if (!waterTiles.Contains(neighbourPos) && neighbourPos != startPos && tilemap.GetTile(neighbourPos)) 
                { 
                    if (x != 0 || y != 0)
                    {
                        Node neighbour = GetNode(neighbourPos);
                        neighbours.Add(neighbour);
                    }
                }
            }
        }
        return neighbours;
    }
    private void CheckNeighbours(List<Node> neighbours, Node curNode)
    {
        foreach (Node neighbour in neighbours)
        {
            if (!CanMoveInDiagonals(neighbour, curNode)) 
            {
                continue;
            }
            int gScore = DetermineGScore(neighbour.Position, curNode.Position);
            if (openNodes.Contains(neighbour))
            {
                if (curNode.G + gScore < neighbour.G)
                {
                    CalculateValues(curNode, neighbour, gScore);
                }
            }
            else if (!closedNodes.Contains(neighbour))
            {
                CalculateValues(curNode, neighbour, gScore);
                openNodes.Add(neighbour);
            }

            }
    }
    private bool CanMoveInDiagonals(Node cur, Node neighbour) 
    {
        Vector3Int direction=cur.Position - neighbour.Position;
        Vector3Int first = new Vector3Int((cur.Position.x-direction.x),cur.Position.y, cur.Position.z );
        Vector3Int second= new Vector3Int(cur.Position.x, (cur.Position.y - direction.y), cur.Position.z);
        if (waterTiles.Contains(first) || waterTiles.Contains(second)) {
            return false;
        }
        return true;
    }
    private void CalculateValues(Node parent, Node neighbour, int cost)
    {
        neighbour.Parent= parent;
        neighbour.G=parent.G+cost;
        neighbour.H=(Math.Abs(neighbour.Position.x-targetPos.x) + Math.Abs(neighbour.Position.y-targetPos.y))*10;
        neighbour.F= neighbour.G+neighbour.H;

    }
    private int DetermineGScore(Vector3Int neighbour, Vector3Int current)
    {
        int gScore = 0;
        int x = current.x-neighbour.x;
        int y = current.y-neighbour.y;
        if ((Math.Abs(x - y) % 2) == 1)
        {
            gScore = 10;
        }
        else 
        { 
            gScore= 14;
        }
        return gScore;
    }
    private void UpdateCurNode(ref Node curNode)
    {
        closedNodes.Add(curNode);
        openNodes.Remove(curNode);
        if (openNodes.Count > 0)
        {
            curNode=openNodes.OrderBy(x => x.F).First();
        }
    }
    private Stack<Vector3Int> GeneratePath(Node curNode)
    {
        if(curNode.Position == targetPos)
        {
            Stack<Vector3Int> finalPath= new Stack<Vector3Int>();
            while (curNode.Position != startPos)
            {
                finalPath.Push(curNode.Position);
                curNode=curNode.Parent;
            }
            return finalPath;
        }
        return null;
    }
    private void AstarAlgorithm()
    {
        if (currentNode == null)
        {
            Initialize();
        }
        while (openNodes.Count > 0 && path is null)
        {
            List<Node> neighbours = GetNeighbours(currentNode.Position);
            CheckNeighbours(neighbours, currentNode);
            UpdateCurNode(ref currentNode);
            path=GeneratePath(currentNode);
            
        }
        if(path != null)
        {
            foreach(Vector3Int step in path)
            {
                if (step != targetPos)
                {
                    tilemap.SetTile(step, tiles[4]);
                }
            }
        }
        
        AstarDebug.MyInstance.CreateTiles(startPos, targetPos,allNodes, openNodes, closedNodes, path);

    }
    public void Restart()
    {
        foreach(Vector3Int pos in allNodes.Keys)
        {
            tilemap.SetTile(pos, null);
        }
        foreach(Vector3Int pos in changedNodes)
        {
            tilemap.SetTile(pos, tiles[5]);
        }
    }
}
